package com.cman777.springc.redis.aspect;

import com.cman777.springc.common.helper.AopUtil;
import com.cman777.springc.common.helper.SpelUtil;
import com.cman777.springc.redis.annotation.RedisLock;
import com.cman777.springc.redis.bean.dto.RedisLockInfo;
import com.cman777.springc.redis.util.RedisLockUtil;

import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author chenzhicong
 * @time 2019/11/10 15:54
 * @description
 * 标志order的原因是为了让锁在事务前面，在缓存切面的后面，需要保证order值小于Integer.Max
 */
@Log4j2
@Aspect
@Component
@Order(2)
public class RedisLockAspect {
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 定义切面
     */
    @Pointcut("@annotation(com.cman777.springc.redis.annotation.RedisLock)" +
            " || @annotation(com.cman777.springc.redis.annotation.RedisLock.List)")
    public void redisLock() {}

    /**
     * around切面
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("redisLock()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        Object[] args = proceedingJoinPoint.getArgs();

        Method method = AopUtil.around2Method(proceedingJoinPoint);

        List<RedisLockInfo> redisLockInfoList = new ArrayList<>(packageRedissonLockKey(method,args));

        /**
         * 加锁与解锁
         */
        int siz = redisLockInfoList.size();
        RLock[] rLocks = new RLock[redisLockInfoList.size()];
        try {
            for (int i = 0;i < siz;i ++) {
                RedisLockInfo redisLockInfo = redisLockInfoList.get(i);
                rLocks[i] = redissonClient.getFairLock(redisLockInfo.getKey());
                RedisLockUtil.lock(rLocks[i],redisLockInfo.getWaitTime(),redisLockInfo.getLeaseTime());
            }
            return proceedingJoinPoint.proceed();
        } finally {
            for (RLock rLock : rLocks) {
                RedisLockUtil.unlock(rLock);
            }
        }
    }

    /**
     *
     * @param method
     * @param args
     * @return
     */
    private static Set<RedisLockInfo> packageRedissonLockKey(Method method, Object[] args) {

        List<RedisLock> redisLockList = null;
        /**
         * 取出方法的 @RedisLock 注解的信息
         */
        RedisLock.List LockList = method.getAnnotation(RedisLock.List.class);
        if (null != LockList) {
            redisLockList = new ArrayList<>();
            Collections.addAll(redisLockList, LockList.value());
        } else {
            RedisLock redisLock = method.getAnnotation(RedisLock.class);
            if (null != redisLock) {
                redisLockList = new ArrayList<>();
                redisLockList.add(redisLock);
            }
        }
        Validate.isTrue(!CollectionUtils.isEmpty(redisLockList));

        Set<RedisLockInfo> result = new LinkedHashSet<>();

        for (RedisLock redisLock : redisLockList) {
            /**
             * 锁key的前缀
             */
            String prefix = null;

            if (!redisLock.entity().isInterface()) {
                prefix = redisLock.entity().getName();
            } else {
                if (StringUtils.isNotBlank(redisLock.fixedPrefix())) {
                    prefix = redisLock.fixedPrefix();
                }
            }
            Validate.isTrue(StringUtils.isNotBlank(prefix));

            String[] salts = null;
            if (args.length > 0) {
                /**
                 * 用spel解析盐
                 */
                salts = SpelUtil.parse(redisLock.salt(),method,args)
                        .split(",");
            } else {
                salts = redisLock.salt().split(",");
            }

            for (String salt : salts) {
                RedisLockInfo redisLockInfo = new RedisLockInfo();
                redisLockInfo.setKey(RedisLockUtil.packageRedissonLockKey(prefix,salt));
                redisLockInfo.setWaitTime(redisLock.waitTime());
                redisLockInfo.setLeaseTime(redisLock.leaseTime());
                result.add(redisLockInfo);
            }
        }
        return result;
    }

}
